package com.music.lib

import com.example.helloworld.HelloWorld
import com.squareup.javapoet.*
import javassist.*
import javassist.bytecode.Descriptor
import javassist.expr.ExprEditor
import javassist.expr.MethodCall
import java.io.File
import java.util.*


/**
 * @author Afra55
 * @date 2020/8/5
 * A smile is the best business card.
 * 没有成绩，连呼吸都是错的。
 */
internal object TextJava {
    @JvmStatic
    fun main(args: Array<String>) {
        println(System.getenv("PUBLISH_RELEASE"))

//        createUserClass()

//        changeCurrentClass()

//        javapoetTestClass()

        val android_home = System.getenv().get("ANDROID_HOME")
        System.out.println(android_home)
        testInstrument()

    }

    private fun testInstrument() {
        val classPoll = ClassPool.getDefault()
        val cc = classPoll.get(MyGirl::class.java.name)
        val hc = classPoll.get(HelloWorld::class.java.name)
        for (referencedType in cc.refClasses) {
            println(referencedType.toString())
        }
        cc.instrument(object : ExprEditor() {
            override fun edit(m: MethodCall) {
                if (m.className == HelloWorld::class.java.name) {
                    println("${m.className} ${m.methodName} ${m.signature}")

                    try {
                        val a = hc.getMethod(m.methodName, m.signature)
                    } catch (e: Exception) {
                        e.printStackTrace()
                    }
                }

            }
        })
    }

    /**
     * 基础使用方法
     */
    @JvmStatic
    fun createUserClass() {
        // 获得一个ClassPool对象，该对象使用Javassist控制字节码的修改
        val classPoll = ClassPool.getDefault()

        // 创建一个类
        val cc = classPoll.makeClass("com.oh.my.god.User")

        // 创建一个属性 private String name;
        val nameField = CtField(classPoll.get(java.lang.String::class.java.name), "name", cc)
        // 修饰符
        nameField.modifiers = Modifier.PRIVATE
        // 把属性添加到类中
        cc.addField(nameField, CtField.Initializer.constant("Afra55"))

        // 添加 get set 方法
        cc.addMethod(CtNewMethod.setter("setName", nameField))
        cc.addMethod(CtNewMethod.setter("getName", nameField))

        // 无参数构造函数
        val cons = CtConstructor(arrayOf<CtClass>(), cc)
        // 设置函数内容, name 是上面添加的属性
        cons.setBody("{name = \"无参构造\";}")
        // 把构造函数添加到类中
        cc.addConstructor(cons)

        // 一个参数的构造函数
        val cons1 =
            CtConstructor(arrayOf<CtClass>(classPoll.get(java.lang.String::class.java.name)), cc)
        // $0=this / $1,$2,$3... 代表方法参数
        cons1.setBody("{$0.name = $1;}")
        // 把构造函数添加到类中
        cc.addConstructor(cons1)

        // 创建一个 singASong 方法, CtMethod(返回类型，方法名，参数)
        val myMethod = CtMethod(CtClass.voidType, "singASong", arrayOf<CtClass>(), cc)
        myMethod.modifiers = Modifier.PUBLIC
        myMethod.setBody("{System.out.println(name);}")
        cc.addMethod(myMethod)

        // 创建 .class 文件，可传入路径
        cc.writeFile()

        // toClass : 将修改后的CtClass加载至当前线程的上下文类加载器中，CtClass的toClass方法是通过调用本方法实现。需要注意的是一旦调用该方法，则无法继续修改已经被加载的class；
        // cc.toClass()

        // 冻结一个类，使其不可修改；
        // cc.freeze()

        // 删除类不必要的属性
        // cc.prune()

        //  解冻一个类，使其可以被修改
        // cc.defrost()

        // 将该class从ClassPool中删除
        // cc.detach()


    }

    /**
     * 对已有类的修改
     */
    @JvmStatic
    fun changeCurrentClass() {
        val pool = ClassPool.getDefault()
        val cc = pool.get("com.music.lib.MyGirl")

        System.out.println(cc.name)
        System.out.println(MyGirl::class.java.name)

        val myMethod = cc.getDeclaredMethod("play")
        myMethod.insertBefore("System.out.println(\"insertBefore\");")
        myMethod.insertAfter("System.out.println(\"insertAfter\");")

        val classMap = ClassMap()
        classMap[Descriptor.toJvmName("com.music.lib.MyGirl")] =
            Descriptor.toJvmName("com.oh.my.girl.Wife")

        cc.replaceClassName(classMap)

        cc.toClass()

        cc.writeFile()

    }

    /**
     * 使用工具生成类
     */
    @JvmStatic
    fun javapoetTestClass() {
        // 创建一个方法 main
        val main = MethodSpec.methodBuilder("main")
            // 创建修饰符 public static
            .addModifiers(
                javax.lang.model.element.Modifier.PUBLIC,
                javax.lang.model.element.Modifier.STATIC
            )
            // 返回类型
            .returns(Void.TYPE)
            // 参数
            .addParameter(Array<String>::class.java, "args")
            // 添加内容
            .addStatement("\$T.out.println(\$S)", System::class.java, "Hello, JavaPoet!")
            .addStatement("int total = 0")
            // 代码块条件语句
            .beginControlFlow("for(int i = 0; i < 10; i++)")
            // 代码块内容
            .addStatement("total += i")
            // 代码块结束
            .endControlFlow()
            .build()

        // 创建方法 add， 注意下面的 $S 代表字符串会被引号扩起来并被转义， $T 代表类型， $L 代表参数不会被转义为字符串
        val addMethod = MethodSpec.methodBuilder("add")
            .addModifiers(javax.lang.model.element.Modifier.PUBLIC)
            .returns(Integer.TYPE)
            .addParameter(Integer.TYPE, "number")
            .addParameter(Integer.TYPE, "sub")
            .beginControlFlow("for(int i = \$L; i < \$L; i++)", 0, 10)
            .addStatement("number += i + sub")
            .endControlFlow()
            .beginControlFlow("if (number > 10)")
            .addStatement("number *= \$L", 20)
            .nextControlFlow("if (number > 5)")
            .addStatement("number -= 10")
            .nextControlFlow("else")
            .addStatement(
                "\$T.out.println(\$S)",
                System::class.java,
                "Ok, time still moving forward \"\$ @@"
            )
            .addStatement(
                "\$T.out.println(\$S)",
                System::class.java,
                12345
            )
            .endControlFlow()
            .addStatement("return number")
            .build()

        val catchMethod = MethodSpec.methodBuilder("catchMethod")
            .beginControlFlow("try")
            .addStatement("throw new Exception(\$S)", "Failed")
            .nextControlFlow("catch (\$T e)", Exception::class.java)
            .addStatement("throw new \$T(e)", RuntimeException::class.java)
            .endControlFlow()
            .build()

        // 返回 Date 对象的方法
        val today: MethodSpec = MethodSpec.methodBuilder("today")
            .returns(Date::class.java)
            .addStatement("return new \$T()", Date::class.java)
            .build()

        val hoverboard: ClassName = ClassName.get("java.util", "Date")
        val tomorrow: MethodSpec = MethodSpec.methodBuilder("tomorrow")
            .returns(hoverboard)
            .addStatement("return new \$T()", hoverboard)
            .build()

        // 生成一个 hexDigit 方法
        val hexDigit = MethodSpec.methodBuilder("hexDigit")
            .addParameter(Int::class.javaPrimitiveType, "i")
            .returns(Char::class.javaPrimitiveType)
            .addStatement("return (char) (i < 10 ? i + '0' : i - 10 + 'a')")
            .build()

        // 通过 $N 来引用 hexDigit() 方法
        val byteToHex = MethodSpec.methodBuilder("byteToHex")
            .addParameter(Int::class.javaPrimitiveType, "b")
            .returns(String::class.java)
            .addStatement("char[] result = new char[2]")
            .addStatement("result[0] = \$N((b >>> 4) & 0xf)", hexDigit)
            .addStatement("result[1] = \$N(b & 0xf)", hexDigit)
            .addStatement("return new String(result)")
            .build()

        // 静态变量, 通过 JavaFile 添加静态引用,详情往下看
        val girl = ClassName.get("com.music.lib", "MyGirl")
        val staticTestMethod = MethodSpec.methodBuilder("staticTestMethod")
            .addStatement(
                "\$T.out.println(\$T.A)",
                System::class.java,
                girl
            )
            .build()

        // 创建一个空参构造函数, $N 引用已声明的属性
        val constructor: MethodSpec = MethodSpec.constructorBuilder()
            .addModifiers(javax.lang.model.element.Modifier.PUBLIC)
            .addStatement("this.\$N = \$S", "greeting", 90909)
            .build()

        // 创建一个带参构造函数
        val constructor1: MethodSpec = MethodSpec.constructorBuilder()
            .addModifiers(javax.lang.model.element.Modifier.PUBLIC)
            .addParameter(String::class.java, "greeting")
            .addStatement("this.\$N = \$N", "greeting", "greeting")
            .build()

        // javadoc
        val map = linkedMapOf<String, Any>()
        map["author"] = "Afra55"
        map["date"] = "2020.8.6"
        map["desc"] = "你说一，我说一，大家都来说一个"
        map["version"] = 1.0


        // 创建类HelloWorld
        val helloWorld = TypeSpec.classBuilder("HelloWorld")
            .addModifiers(
                javax.lang.model.element.Modifier.PUBLIC,
                javax.lang.model.element.Modifier.FINAL
            )
            // 添加 javadoc
            .addJavadoc(
                CodeBlock.builder().addNamed(
                    "Author: \$author:S\nDate: \$date:S\nDesc: \$desc:S\nVersion: \$version:L",
                    map
                )
                    .build()
            )
            .addJavadoc("\n")

            // 添加一个属性 greeting
            .addField(
                String::class.java,
                "greeting",
                javax.lang.model.element.Modifier.PRIVATE,
                javax.lang.model.element.Modifier.FINAL
            )

            // 添加一个初始化值的属性 version
            .addField(
                FieldSpec.builder(String::class.java, "version")
                    .addModifiers(
                        javax.lang.model.element.Modifier.PRIVATE,
                        javax.lang.model.element.Modifier.FINAL
                    )
                    // 初始化值
                    .initializer("\$S + \$L", "Afra55-", 1.0)
                    .build()
            )

            // 添加方法
            .addMethod(constructor)
            .addMethod(constructor1)
            .addMethod(main)
            .addMethod(addMethod)
            .addMethod(catchMethod)
            .addMethod(today)
            .addMethod(tomorrow)
            .addMethod(staticTestMethod)
            .addMethod(hexDigit)
            .addMethod(byteToHex)
            .build()

        val javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
            // 添加静态引用
            .addStaticImport(girl, "*")
            .build()

        // 输出到控制台
        javaFile.writeTo(System.out)

        // 输出到文件
        javaFile.writeTo(File("/Users/victor/Program/Android/Demo/testJavaLib/lib/src/main/java/"))
    }

    /**
     * 使用工具生成接口
     */
    @JvmStatic
    fun javapoetTestInterfaceClass() {
        val helloWorld = TypeSpec.interfaceBuilder("HelloWorld")
            .addModifiers(javax.lang.model.element.Modifier.PUBLIC)
            .addField(
                FieldSpec.builder(String::class.java, "ONLY_THING_THAT_IS_CONSTANT")
                    .addModifiers(
                        javax.lang.model.element.Modifier.PUBLIC,
                        javax.lang.model.element.Modifier.STATIC,
                        javax.lang.model.element.Modifier.FINAL
                    )
                    .initializer("\$S", "change")
                    .build()
            )
            .addMethod(
                MethodSpec.methodBuilder("beep")
                    .addModifiers(
                        javax.lang.model.element.Modifier.PUBLIC,
                        javax.lang.model.element.Modifier.ABSTRACT
                    )
                    .build()
            )
            .build()
    }
}